// https://github.com/lifesinger/dew/blob/master/lib/es5-safe/src/es5-safe.js
// es5-safe
// ----------------
// Provides compatibility shims so that legacy JavaScript engines behave as
// closely as possible to ES5.
//
// Thanks to:
//  - http://es5.github.com/
//  - http://kangax.github.com/es5-compat-table/
//  - https://github.com/kriskowal/es5-shim
//  - http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/
//  - https://gist.github.com/1120592
//  - https://code.google.com/p/v8/


var OP = Object.prototype;
var AP = Array.prototype;
var FP = Function.prototype;
var SP = String.prototype;
var hasOwnProperty = OP.hasOwnProperty;
var slice = AP.slice;


/*---------------------------------------*
 * Function
 *---------------------------------------*/

// ES-5 15.3.4.5
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
FP.bind || (FP.bind = function(that) {
  var target = this;

  // If IsCallable(func) is false, throw a TypeError exception.
  if (typeof target !== 'function') {
    throw new TypeError('Bind must be called on a function');
  }

  var boundArgs = slice.call(arguments, 1);

  function bound() {
    // Called as a constructor.
    if (this instanceof bound) {
      var self = createObject(target.prototype);
      var result = target.apply(
          self,
          boundArgs.concat(slice.call(arguments))
      );
      return Object(result) === result ? result : self;
    }
    // Called as a function.
    else {
      return target.apply(
          that,
          boundArgs.concat(slice.call(arguments))
      );
    }
  }

  // NOTICE: The function.length is not writable.
  //bound.length = Math.max(target.length - boundArgs.length, 0);

  return bound;
});


// Helpers
function createObject(proto) {
  var o;

  if (Object.create) {
    o = Object.create(proto);
  }
  else {
    /** @constructor */
    function F() {
    }

    F.prototype = proto;
    o = new F();
  }

  return o;
}


/*---------------------------------------*
 * Object
 *---------------------------------------*/
// http://ejohn.org/blog/ecmascript-5-objects-and-properties/

// ES5 15.2.3.14
// http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
// https://developer.mozilla.org/en/ECMAScript_DontEnum_attribute
// http://msdn.microsoft.com/en-us/library/adebfyya(v=vs.94).aspx
Object.keys || (Object.keys = (function() {
  var hasDontEnumBug = !{toString: ''}.propertyIsEnumerable('toString');
  var DontEnums = [
    'toString',
    'toLocaleString',
    'valueOf',
    'hasOwnProperty',
    'isPrototypeOf',
    'propertyIsEnumerable',
    'constructor'
  ];
  var DontEnumsLength = DontEnums.length;

  return function(o) {
    if (o !== Object(o)) {
      throw new TypeError(o + ' is not an object');
    }

    var result = [];

    for (var name in o) {
      if (hasOwnProperty.call(o, name)) {
        result.push(name);
      }
    }

    if (hasDontEnumBug) {
      for (var i = 0; i < DontEnumsLength; i++) {
        if (hasOwnProperty.call(o, DontEnums[i])) {
          result.push(DontEnums[i]);
        }
      }
    }

    return result;
  };

})());


/*---------------------------------------*
 * Array
 *---------------------------------------*/
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array
// https://github.com/kangax/fabric.js/blob/gh-pages/src/util/lang_array.js

// ES5 15.4.3.2
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
Array.isArray || (Array.isArray = function(obj) {
  return OP.toString.call(obj) === '[object Array]';
});


// ES5 15.4.4.18
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
AP.forEach || (AP.forEach = function(fn, context) {
  for (var i = 0, len = this.length >>> 0; i < len; i++) {
    if (i in this) {
      fn.call(context, this[i], i, this);
    }
  }
});


// ES5 15.4.4.19
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map
AP.map || (AP.map = function(fn, context) {
  var len = this.length >>> 0;
  var result = new Array(len);

  for (var i = 0; i < len; i++) {
    if (i in this) {
      result[i] = fn.call(context, this[i], i, this);
    }
  }

  return result;
});


// ES5 15.4.4.20
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter
AP.filter || (AP.filter = function(fn, context) {
  var result = [], val;

  for (var i = 0, len = this.length >>> 0; i < len; i++) {
    if (i in this) {
      val = this[i]; // in case fn mutates this
      if (fn.call(context, val, i, this)) {
        result.push(val);
      }
    }
  }

  return result;
});


// ES5 15.4.4.16
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/every
AP.every || (AP.every = function(fn, context) {
  for (var i = 0, len = this.length >>> 0; i < len; i++) {
    if (i in this && !fn.call(context, this[i], i, this)) {
      return false;
    }
  }
  return true;
});


// ES5 15.4.4.17
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/some
AP.some || (AP.some = function(fn, context) {
  for (var i = 0, len = this.length >>> 0; i < len; i++) {
    if (i in this && fn.call(context, this[i], i, this)) {
      return true;
    }
  }
  return false;
});


// ES5 15.4.4.21
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce
AP.reduce || (AP.reduce = function(fn /*, initial*/) {
  if (typeof fn !== 'function') {
    throw new TypeError(fn + ' is not an function');
  }

  var len = this.length >>> 0, i = 0, result;

  if (arguments.length > 1) {
    result = arguments[1];
  }
  else {
    do {
      if (i in this) {
        result = this[i++];
        break;
      }
      // if array contains no values, no initial value to return
      if (++i >= len) {
        throw new TypeError('reduce of empty array with on initial value');
      }
    }
    while (true);
  }

  for (; i < len; i++) {
    if (i in this) {
      result = fn.call(null, result, this[i], i, this);
    }
  }

  return result;
});


// ES5 15.4.4.22
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight
AP.reduceRight || (AP.reduceRight = function(fn /*, initial*/) {
  if (typeof fn !== 'function') {
    throw new TypeError(fn + ' is not an function');
  }

  var len = this.length >>> 0, i = len - 1, result;

  if (arguments.length > 1) {
    result = arguments[1];
  }
  else {
    do {
      if (i in this) {
        result = this[i--];
        break;
      }
      // if array contains no values, no initial value to return
      if (--i < 0)
        throw new TypeError('reduce of empty array with on initial value');
    }
    while (true);
  }

  for (; i >= 0; i--) {
    if (i in this) {
      result = fn.call(null, result, this[i], i, this);
    }
  }

  return result;
});


// ES5 15.4.4.14
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/indexOf
AP.indexOf || (AP.indexOf = function(value, from) {
  var len = this.length >>> 0;

  from = Number(from) || 0;
  from = Math[from < 0 ? 'ceil' : 'floor'](from);
  if (from < 0) {
    from = Math.max(from + len, 0);
  }

  for (; from < len; from++) {
    if (from in this && this[from] === value) {
      return from;
    }
  }

  return -1;
});


// ES5 15.4.4.15
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/indexOf
AP.lastIndexOf || (AP.lastIndexOf = function(value, from) {
  var len = this.length >>> 0;

  from = Number(from) || len - 1;
  from = Math[from < 0 ? 'ceil' : 'floor'](from);
  if (from < 0) {
    from += len;
  }
  from = Math.min(from, len - 1);

  for (; from >= 0; from--) {
    if (from in this && this[from] === value) {
      return from;
    }
  }

  return -1;
});


/*---------------------------------------*
 * String
 *---------------------------------------*/

// ES5 15.5.4.20
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/trim
// http://blog.stevenlevithan.com/archives/faster-trim-javascript
// http://jsperf.com/mega-trim-test
SP.trim || (SP.trim = (function() {

  // http://perfectionkills.com/whitespace-deviations/
  var whiteSpaces = [

    '\\s',
    //'0009', // 'HORIZONTAL TAB'
    //'000A', // 'LINE FEED OR NEW LINE'
    //'000B', // 'VERTICAL TAB'
    //'000C', // 'FORM FEED'
    //'000D', // 'CARRIAGE RETURN'
    //'0020', // 'SPACE'

    '00A0', // 'NO-BREAK SPACE'
    '1680', // 'OGHAM SPACE MARK'
    '180E', // 'MONGOLIAN VOWEL SEPARATOR'

    '2000-\\u200A',
    //'2000', // 'EN QUAD'
    //'2001', // 'EM QUAD'
    //'2002', // 'EN SPACE'
    //'2003', // 'EM SPACE'
    //'2004', // 'THREE-PER-EM SPACE'
    //'2005', // 'FOUR-PER-EM SPACE'
    //'2006', // 'SIX-PER-EM SPACE'
    //'2007', // 'FIGURE SPACE'
    //'2008', // 'PUNCTUATION SPACE'
    //'2009', // 'THIN SPACE'
    //'200A', // 'HAIR SPACE'

    '200B', // 'ZERO WIDTH SPACE (category Cf)
    '2028', // 'LINE SEPARATOR'
    '2029', // 'PARAGRAPH SEPARATOR'
    '202F', // 'NARROW NO-BREAK SPACE'
    '205F', // 'MEDIUM MATHEMATICAL SPACE'
    '3000' //  'IDEOGRAPHIC SPACE'

  ].join('\\u');

  var trimLeftReg = new RegExp('^[' + whiteSpaces + ']+');
  var trimRightReg = new RegExp('[' + whiteSpaces + ']+$');

  return function() {
    return String(this).replace(trimLeftReg, '').replace(trimRightReg, '');
  }

})());


/*---------------------------------------*
 * Date
 *---------------------------------------*/

// ES5 15.9.4.4
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/now
Date.now || (Date.now = function() {
  return +new Date;
});